package com.example.mq.mqServer.dao;


import com.example.mq.mqServer.core.MSGQueue;
import com.example.mq.mqServer.core.message.Message;
import com.example.mq.mqServer.exception.MqException;
import com.example.mq.mqServer.util.FileUtils;
import com.example.mq.mqServer.util.SerializableUtils;
import lombok.extern.slf4j.Slf4j;


import java.io.*;
import java.util.LinkedList;
import java.util.Scanner;

/**
 * @BelongsProject: 0.mq
 * @BelongsPackage: com.example.mq.mqServer.dao
 * @Author : 游在海里的鱼
 * @CreateTime : 2023-12-11  21:55
 * @Version : 1.0
 * @CLassName : 消息文件管理者
 * @Description : 操控文件来实现消息的持久化
 */
@Slf4j
public class MessageFileManager {

    //内部类-用于记录队列的消息总数和有效信息总数
     private class Stat{
         //消息总数
         public int totalCount;
         //有效消息总数
         public int validCount;

         public Stat(int totalCount ,int validCount){
             this.totalCount = totalCount;
             this.validCount = validCount;
         }
     }


     /** ---------------------------------------------------------------------------------------------------------------------
      * @Method  : 对象初始化
      * @Describe: TODO
      * @modifier: [public] [实例方法]
     **/
     public void init(){
         log.info("[init] [消息文件管理者]初始化完成");
     }



     /** ---------------------------------------------------------------------------------------------------------------------
      * @Method  : 操作队列目录
      * @Describe: 增,删,查存在,
      * @modifier: [public] [实例方法]
     **/
     public boolean createQueue(String queueName){
         //1.获取此队列的目录文件
         File queueDir = new File(getQueueDir(queueName));
         //2.目录已存在则创建失败
         if(queueDir.exists()){
             new MqException("[MessageFileManager] [createQueue] " +
                     "[目录:"+queueDir.getName()+"]创建失败，因为已存在 ").printStackTrace();
             return false;
         }else {
         //3.创建队列目录,消息统计信息文件,消息数据文件
             if(!queueDir.mkdir()){
                 new MqException("[MessageFileManager] [createQueue] " +
                         "[目录:"+queueDir.getName()+"]mkdir()失败").printStackTrace();
                 return false;
             }

             try {
                 new File(getQueueStatPath(queueName)).createNewFile();
             } catch (IOException e) {
                 log.error("[createQueue] [目录:"+queueName+"]的[messageStat.txt]创建失败");
                 e.printStackTrace();
             }
             
             try {
                 new File(getQueueMessagePath(queueName)).createNewFile();
             } catch (IOException e) {
                 log.error("[createQueue] [目录:"+queueName+"]的[messageData.txt]创建失败");
                 e.printStackTrace();
             }

             //4.消息统计信息文件初始化(0,0)
             writeStat(queueName,new Stat(0,0));
             //5.打印日志
             log.debug("[createQueue] [目录:"+queueDir.getName()+"]创建成功");
             return true;
         }
     }

     public boolean deleteQueue(String queueName) {
         //1.获得队列相关文件的对象
         File queueDir = new File(getQueueDir(queueName));
         //2.删除队列目录下的所有文件
         FileUtils.deleteDirectory(queueDir);
         //3.判断目录是否存在
         if(!queueDir.exists()){
             log.debug("[deleteQueue] [目录:"+queueDir.getName()+"]删除成功");
             return true;
         }
         //4.删除队列目录失败
         new MqException("[MessageFileManager] [deleteQueue] " +
                 "[目录:"+queueDir.getName()+"]删除失败").printStackTrace();
         return false;
     }

     public boolean checkQueue(String queueName)  {
         //1.获得队列目录
         File queueDir = new File(getQueueDir(queueName));
         if(queueDir.exists()){
             if(!new File(getQueueStatPath(queueName)).exists()){
                 new MqException("[MessageFileManager] [checkQueue] " +
                         "[目录:"+queueName+"]的[messageStat.txt]不存在").printStackTrace();
                 return false;
             }
             if(!new File(getQueueMessagePath(queueName)).exists()){
                 new MqException("[MessageFileManager] [checkQueue] " +
                         "[目录:"+queueName+"]的[messageData.txt]不存在").printStackTrace();
                 return false;
             }
             log.debug("[检查队列] [目录:"+queueDir.getName()+"]检查无误");
             return true;
         }
         new MqException("[MessageFileManager] [checkQueue] [目录:"+queueName+"]不存在").printStackTrace();
         return false;
     }


    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 往消息文件里写入消息
     * @Describe: 每个消息在消息文件中的存储格式:[消息长度:4字节] [消息字节流:n字节] [逻辑删除:1字节(0无效1有效)]
     * @modifier: [public] [实例方法]
    **/
    public boolean insertMessage(MSGQueue queue, Message message){
        //1.检查队列文件是否存在
        checkQueue(queue.getName());
        //2.序列化message
        byte[] messageBytes = new byte[0];
        try {
            messageBytes = SerializableUtils.toBytes(message);
        } catch (IOException e) {
            log.error("[insertMessage] [消息:"+ message.getId()+ "]序列化失败");
            e.printStackTrace();
        }
        //3.队列对象进行枷锁，避免线程冲突
        synchronized (queue) {
        //4.获得message的File对象
            File messageFile = new File(getQueueMessagePath(queue.getName()));
        //5.获得文件的字节流
            //因为这里是追加而不是覆写，所以参数要加上true
            try (FileOutputStream fileOutStream = new FileOutputStream(messageFile, true)) {
        //6.设置message的起始和结尾,[消息字节流]的开头和结尾
                //+4是因为记录message长度的有四个字节
                message.setOffsetBeg(messageFile.length() + 4);
                message.setOffsetEnd(messageFile.length() + 4 + messageBytes.length);
        //7.将消息的追加进文件
                //使用DataOutStream里的方法，将完整的将int类型的消息长度的四个字节完整的输入到文件里
                try (DataOutputStream dataOutStream = new DataOutputStream(fileOutStream)) {
                    //+1是因为多了一个尾部加上的用来进行逻辑删除的标记
                    dataOutStream.writeInt(messageBytes.length);
                    dataOutStream.write(messageBytes);
                    dataOutStream.writeByte(1);
                }
            } catch (IOException e) {
                log.error("[insertMessage] [目录:"+queue.getName()+ "] [messageData.txt]字节流写入数据出现异常");
                e.printStackTrace();
            }
            //8.修改Stat文件里的内容
            Stat stat = readStat(queue.getName());
            stat.validCount++;
            stat.totalCount++;
            writeStat(queue.getName(), stat);
            log.debug("[insertMessage] [目录:"+queue.getName()+ "] [messageData.txt]插入消息成功");
            return true;
        }
    }
    
    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 在消息文件中删除某个消息
     * @Describe: 使用的是逻辑删除
     * @modifier: [public] [实例方法]
    **/
    public boolean deleteMessage(MSGQueue queue,Message message){
        //1.对队列对象进行加锁
        synchronized (queue){
        //2.检查队列文件是否存在
            checkQueue(queue.getName());
        //3.得到文件的字节流
            //RandomAccessFile 是用于对文件进行随机访问的类，可以在文件中定位到指定的位置，有两个参数（文件路径，打开方式），rw是读写，r是读，w是写
            try(RandomAccessFile randomOutStream = new RandomAccessFile(getQueueMessagePath(queue.getName()),"rw")){
        //4.光标指向实现逻辑删除的字节，因为光标的小标是从0开始，所以OffsetEnd就指向了逻辑删除位
                randomOutStream.seek(message.getOffsetEnd());
        //5.修改为0，表示删除
                randomOutStream.writeByte(0);
            } catch (IOException e) {
                log.error("[deleteMessage] [目录:"+queue.getName()+ "] [messageData.txt]字节流写入数据出现异常");
                e.printStackTrace();
            }
            //6.修改消息文件的统计信息
            Stat stat = readStat(queue.getName());
            stat.validCount--;
            writeStat(queue.getName(),stat);
            log.debug("[deleteMessage] [目录:"+queue.getName()+ "] [messageData.txt]删除消息成功");
            return true;
        }
    }
    

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 读取消息文件,获取信息对象
     * @Describe: 获得消息文件中,所有有效的消息,并反序列化为消息对象
     * @modifier: [public] [实例方法]
    **/
    public LinkedList<Message> selectAllMessage(String queueName){
        //1.检查队列文件是否存在
        checkQueue(queueName);
        //2.new返回用的Message链表
        LinkedList<Message> ret = new LinkedList<>();
        //4.得到messageDate文件对象
        File MessageFile = new File(getQueueMessagePath(queueName));
        //5.得到文件的输入流
        try (FileInputStream fileInputStream = new FileInputStream(MessageFile)){
        //6.套上DataInputStream来进行读写
            try(DataInputStream dataInputStream = new DataInputStream(fileInputStream)){
        //7.定义光标,用于显示当前字节流读取到哪个字节
                long seek = 0;
        //8.循环读取文件的Message，直到文件抛出EOFException，代表读到文件末尾.
                while(true){
                    //消息的字节长度
                    int messageLen = dataInputStream.readInt();
                    //光标右移动4
                    seek += 4;

                    //Message的字节流缓存
                    byte[] buffer = new byte[messageLen];
                    //如果读取的位数跟长度不一样，则说明格式错误并抛出异常
                    if(dataInputStream.read(buffer) != messageLen){
                        new MqException("[MessageFileManager] [selectAllMessage] " +
                                "[目录:"+queueName+"]的messageData.txt字节流读取格式错误").printStackTrace();
                        return null;
                    }
                    seek += messageLen;

                    //消息的逻辑删除位，判断是否删除
                    byte isValid = dataInputStream.readByte();
                    seek += 1;
                    if(isValid == 0){
                        continue;
                    }

                    //反序列化为Message对象
                    Message message = (Message) SerializableUtils.fromBytes(buffer);
                    //设置开头和结束位置
                    message.setOffsetBeg(seek-(1+messageLen));
                    message.setOffsetEnd(seek-1);
                    message.setIsValid((byte) 1);
                    //添加message到链表
                    ret.add(message);
                }
            }
        } catch (EOFException e){
            //9.这里的异常EOFException并不是错误，而是代表着读取完毕
            log.debug("[selectAllMessage] [目录:"+queueName+"] [messageData.txt]所有消息读取完毕");
        }catch (ClassNotFoundException e) {
            log.error("[selectAllMessage] [目录:"+queueName+"] [messageData.txt]反序列化失败");
            e.printStackTrace();
        } catch (IOException e) {
            log.error("[selectAllMessage] [目录:"+queueName+"] [messageData.txt]读取字节流出现异常");
            e.printStackTrace();
        }
        //10.返回链表
        return ret;
    }


    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 垃圾回收
     * @Describe: 当消息总数达到2000且有效消息数不及一半时，就会新建一个消息文件存储有效消息，然后删除旧消息文件
     * @modifier: [public] [实例方法]
    **/
    public boolean garbageCollection(MSGQueue queue){
        String queueName = queue.getName();
        //1.检验queue
        checkQueue(queueName);
        //2.获取消息统计信息
        Stat stat = readStat(queueName);
        //3.消息数超过2000并且有效消息少于一半，就进行垃圾回收
        if(stat.totalCount>2000 && (double)stat.validCount/(double) stat.totalCount < 0.5){
        //4.对queue加锁
            synchronized (queue){
        //5.加载原文件的对象和有效消息链表
                File oldMessageFile = new File(getQueueMessagePath(queueName));
                LinkedList<Message> messages = selectAllMessage(queueName);
        //6.修改原文件的名称并指向改名后的文件
                oldMessageFile.renameTo(new File(getQueueDir(queueName)+"/buffer.txt"));
                oldMessageFile = new File(getQueueDir(queueName)+"/buffer.txt");
        //7.创建一个新的messageData.txt文件
                File newMessageFile = new File(getQueueMessagePath(queueName));
                try {
                    newMessageFile.createNewFile();
                } catch (IOException e) {
                    log.error("[garbageCollection] [目录:"+queueName+"] [messageData.txt文件]创建失败");
                    e.printStackTrace();
                }

                //8.将有效消息写入到新文件中
                try (FileOutputStream fileOutputStream = new FileOutputStream(newMessageFile)){
                    try(DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)){
                        for(Message message:messages){
                            byte[] messageBytes = SerializableUtils.toBytes(message);
                            dataOutputStream.writeInt(messageBytes.length);
                            dataOutputStream.write(messageBytes);
                            dataOutputStream.writeByte(1);
                        }
                    }
                } catch (IOException e) {
                    log.error("[garbageCollection] [目录:"+queueName+"] [messageData.txt]字节流写入数据出现异常");
                    e.printStackTrace();
                }
                //9.修改新文件的统计信息
                stat.totalCount = messages.size();
                stat.validCount = messages.size();
                writeStat(queueName,stat);
        //10.删除旧的信息文件
                if(!oldMessageFile.delete()){
                    new MqException("[MessageFileManager] [garbageCollection] " +
                            "[目录:"+queueName+"] [旧的messageData.txt文件]删除失败").printStackTrace();
                }
                log.info("[garbageCollection]成功，剩余[有效消息"+messages.size()+"个]");
            }

        }
    return false;
    }


    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取某队列的消息总数
     * @Describe: TODO
     * @modifier: [public] [实例方法]
    **/
    public int getTotalCount(String queueName){
        return readStat(queueName).totalCount;
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取某队列的有效消息个数
     * @Describe: TODO
     * @modifier: [public] [实例方法]
    **/
    public int getValidCount(String queueName){
        return readStat(queueName).validCount;
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 检查消息文件的统计信息
     * @Describe: 比对[messageData.txt]的有效消息个数跟[messageStat.txt]记录的个数是否一致
     * @modifier: [public] [实例方法]
    **/
    public boolean checkTotalCount(String queueName){
        //1.检查队列文件是否存在
        checkQueue(queueName);
        //2.new返回用的Message链表
        int retCount = 0;
        //4.得到messageDate文件对象
        File MessageFile = new File(getQueueMessagePath(queueName));
        //5.得到文件的输入流
        try (FileInputStream fileInputStream = new FileInputStream(MessageFile)){
            //6.套上DataInputStream来进行读写
            try(DataInputStream dataInputStream = new DataInputStream(fileInputStream)){
                //7.定义光标
                long seek = 0;
                //8.循环读取文件的Message，知道文件抛出EOFException，代表读到文件末尾
                while(true){
                    //消息的字节长度
                    int messageLen = dataInputStream.readInt();
                    //光标右移动4
                    seek += 4;

                    //Message的字节流缓存
                    byte[] buffer = new byte[messageLen];
                    //如果读取的位数跟长度不一样，则说明格式错误并抛出异常
                    if(dataInputStream.read(buffer) != messageLen){
                        new MqException("[MessageFileManager] [checkTotalCount] " +
                                "[目录:"+queueName+"] [messageData.txt文件]读取格式错误").printStackTrace();
                    }
                    seek += messageLen;

                    //消息的逻辑删除位，判断是否删除
                    byte isValid = dataInputStream.readByte();
                    seek += 1;

                    retCount++;
                }

            }
        }catch (EOFException e){
            //9.这里的异常EOFException并不是错误，而是代表着读取完毕
            log.trace("[checkTotalCount] [目录:"+queueName+"] [messageData.txt字节流]消息读取完毕");
        } catch (IOException e) {
            log.error("[checkTotalCount] [目录:"+queueName+"] [messageData.txt字节流]读取失败");
            e.printStackTrace();
        }
        if(retCount != getTotalCount(queueName)){
            new MqException("[MessageFileManager] [checkTotalCount] " +
                    "[目录:"+queueName+"] [有效消息个数]检验有误").printStackTrace();
            return false;
        }
        log.debug("[checkTotalCount] [目录:"+queueName+"] [有效消息个数]检验无误");
        return true;
    }

     /* ---------------------------------------------------------------------------------------------------------------------
      * @Method  : 获取队列文件的路径,根据指定队列名
      * @Describe: TODO
      * @modifier: [private] [实例方法]
     **/
     private String getQueueDir(String queueName){
         return "./data/"+queueName;
     }

     /* ---------------------------------------------------------------------------------------------------------------------
      * @Method  : 获取队列文件的消息数据文件路径,根据指定队列名
      * @Describe: TODO
      * @modifier: [private] [实例方法]
     **/
     private String getQueueMessagePath(String queueName){
         return getQueueDir(queueName)+"/messageData.txt";
     }

     /* ---------------------------------------------------------------------------------------------------------------------
      * @Method  : 获取队列文件的消息统计信息文件路径,根据指定队列名
      * @Describe: TODO
      * @modifier: [private] [实例方法]
     **/
     private String getQueueStatPath(String queueName){
         return getQueueDir(queueName)+"/messageStat.txt";
     }

     /* ---------------------------------------------------------------------------------------------------------------------
      * @Method  : 读取和写入队列的消息统计信息文件
      * @Describe: TODO
      * @modifier: [private] [实例方法]
     **/
     private Stat readStat(String queueName)  {
         //1.新建一个Stat对象
         Stat ret = new Stat(0,0);
         try (InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))){
         //2.从消息统计信息文件读取Stat信息
             Scanner scanner = new Scanner(inputStream);
             ret.totalCount = scanner.nextInt();
             ret.validCount = scanner.nextInt();
         } catch (IOException e) {
             log.error("[读取统计信息] [目录:"+queueName+"] [messageStat.txt字节流]读取出错");
             e.printStackTrace();
         }
         log.trace("[读取统计信息] [目录:"+queueName+"] [messageStat.txt]读取信息完成");
         return ret;
     }

     private void writeStat(String queueName,Stat stat)  {
         try (OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))){
             PrintWriter printWriter = new PrintWriter(outputStream);
             printWriter.write(stat.totalCount+"\t"+stat.validCount);
             printWriter.flush();
         } catch (IOException e) {
             log.error("[写入统计信息] [目录:"+queueName+"] [messageStat.txt字节流]写入出错");
             e.printStackTrace();
         }
         log.trace("[写入统计信息] [目录:"+queueName+"] [messageStat.txt]写入信息完成");
     }




}
